﻿using ConfigurationSettings;
using FileSystemControls.ListViewItems;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Security;
using log4net;

namespace FileSystemControls
{

    public class FileSystemEngine
    {
        private static readonly ILog _log = LogManager.GetLogger(typeof(FileSystemEngine));

        #region ** Events **

        //public event EventHandler<FileSystemPathChangingEventArgs> FileListChanging;
        //public event EventHandler<FileSystemPathChangedEventArgs> FileListChanged;
        public event EventHandler<AddItemProgressEventArgs> AddItemProgress;
        public event EventHandler<AddItemCompleteEventArgs> AddItemComplete;

        #endregion

        #region ** Member variables **

        private int _itemFinishedCount;
        private int _itemCount;

        private long _availableFreeSpace;

        private BackgroundWorker _backgroundWorker;

        #endregion

        #region ** Properties **

        public bool IsBusy
        {
            get { return _backgroundWorker.IsBusy; }
        }

        public long AvailableFreeSpace
        {
            get { return _availableFreeSpace; }
        }

        #endregion

        public FileSystemEngine()
        {
            using (ThreadContext.Stacks["NDC"].Push("FileSystemEngine.ctor"))
            {
                _log.DebugFormat("Creating BackgroundWorker.");
                _backgroundWorker = new BackgroundWorker
                    {
                        WorkerReportsProgress = true,
                        WorkerSupportsCancellation = true
                    };

                _log.DebugFormat("Setting event handlers.");
                _backgroundWorker.DoWork += DoSetPath;
                _backgroundWorker.ProgressChanged += BackgroundWorker_ProgressChanged;
                _backgroundWorker.RunWorkerCompleted += BackgroundWorker_RunWorkerCompleted;

            }
        }

        private void BackgroundWorker_ProgressChanged(object argSender, ProgressChangedEventArgs argEvtArgs)
        {
            using (ThreadContext.Stacks["NDC"].Push("BackgroundWorker_ProgressChanged"))
            {
                _log.DebugFormat("Raise AddItemProgress event.");
                AddItemProgress.Raise(this, new AddItemProgressEventArgs(argEvtArgs.ProgressPercentage, (FileSystemListViewItem)argEvtArgs.UserState));
            }
        }

        void BackgroundWorker_RunWorkerCompleted(object argSender, RunWorkerCompletedEventArgs argEvtArgs)
        {
            using (ThreadContext.Stacks["NDC"].Push("BackgroundWorker_RunWorkerCompleted"))
            {
                if (!argEvtArgs.Cancelled && (argEvtArgs.Error == null))
                {
                    _log.DebugFormat("Raise AddItemProgress event at 100%.");
                    AddItemComplete.Raise(this, new AddItemCompleteEventArgs(_availableFreeSpace));
                }
            }
        }

        public void CancelAsync()
        {
            using (ThreadContext.Stacks["NDC"].Push("CancelAsync"))
            {
                _log.DebugFormat("Canceling Background worker.");
                _backgroundWorker.CancelAsync();
            }
        }

        /// <summary>
        /// Sets the path of the engine to the value passed in.
        /// </summary>
        /// <param name="argNewPath">String that represents the new path.</param>
        public void SetPath(string argNewPath)
        {
            using (ThreadContext.Stacks["NDC"].Push("SetPath"))
            {
                _log.DebugFormat("Check if the path is empty.");
                //Make sure the user didn't send us to nowheres-ville
                if (argNewPath == string.Empty)
                {
                    return;
                }

                //FileListChanging.Raise(this, new FileSystemPathChangingEventArgs());

                _itemFinishedCount = 0;

                _log.DebugFormat("Spin up the background worker thread.");
                //Spin this off to a thread
                _backgroundWorker.RunWorkerAsync(argNewPath);

                //FileListChanged.Raise(this, new FileSystemPathChangedEventArgs(argNewPath));

            }
        }

        private void DoSetPath(object argNewPath, DoWorkEventArgs argEvtArgs)
        {
            using (ThreadContext.Stacks["NDC"].Push("SetPath"))
            {
                DirectoryInfo currentDirInfo;

                string newPath = argEvtArgs.Argument.ToString();
                //_currentPath = newPath;

                //Create a DirectoryInfo object.
                //This will test if the path is valid at all. This will only fail if the directory is a UNC server
                try
                {
                    _log.DebugFormat("Test if we can make a DirectoryInfo from {0}.", newPath);
                    currentDirInfo = new DirectoryInfo(newPath);
                }
                catch (ArgumentException)
                {
                    _log.DebugFormat("Enumerate shares because this is a UNC name. {0}.", newPath);
                    //This will only occur if the newPath is a network server i.e. "\\HANK_PC"
                    EnumerateShares(newPath);
                    //_availableFreeSpace = 0;
                    return;
                }

                _log.DebugFormat("Get the available free space. This will fail if {0} is a UNC path.", newPath);
                //Create a DriveInfo object to get the AvailableSpace for this.
                //This will fail if this is a UNC Path but there's no real harm other than loosing the AvailableSpace
                if (!GetAvailableFreeSpace(newPath)) return;

                _log.DebugFormat("Add the DotDot entry, if it is required.");
                AddDotDot(currentDirInfo);

                List<FileSystemInfo> fileSystemInfoList = new List<FileSystemInfo>();

                try
                {
                    _log.DebugFormat("Adding FileSystemInfos to list.");
                    fileSystemInfoList.AddRange(currentDirInfo.GetFileSystemInfos());
                }
                catch (ArgumentNullException)
                {
                    _log.DebugFormat("ArgumentNullException.");
                    return;
                }
                catch (SecurityException)
                {
                    _log.DebugFormat("SecurityException.");
                    return;
                }
                catch (ArgumentException)
                {
                    _log.DebugFormat("ArgumentException.");
                    return;
                }
                catch (PathTooLongException)
                {
                    _log.DebugFormat("PathTooLongException.");
                    return;
                }
                catch (UnauthorizedAccessException)
                {
                    _log.DebugFormat("UnauthorizedAccessException.");
                    FileSystemListViewItem currentListViewItem = new FileSystemListViewItem("* Access Denied *");
                    currentListViewItem.SubItems.Add("...");
                    currentListViewItem.SubItems.Add("...");

                    _backgroundWorker.ReportProgress(100, currentListViewItem);

                    currentListViewItem.Dispose();

                    return;
                }

                _log.DebugFormat("Got {0} entries to add.", fileSystemInfoList.Count);
                _itemCount = fileSystemInfoList.Count;

                try
                {
                    _log.DebugFormat("Report items to the ListView for adding.");
                    AddItems(fileSystemInfoList);
                }
                catch (OperationCanceledException)
                {
                    _log.DebugFormat("Operation Canceled.");
                }

            }
        }

        private void AddDotDot(DirectoryInfo argCurrentDirInfo)
        {
            using (ThreadContext.Stacks["NDC"].Push("AddDotDot"))
            {
                _log.DebugFormat("Check if this is a root directory.");
                //Check if this is a root directory
                if (argCurrentDirInfo.Root.FullName != argCurrentDirInfo.FullName)
                {
                    _log.DebugFormat("If it is create a DotDot item to navigate back up.");
                    //Create ".." item to navigate back up the tree
                    DotDotListViewItem currentListViewItem = new DotDotListViewItem(argCurrentDirInfo.Parent);
                    currentListViewItem.SubItems.Add("");
                    currentListViewItem.SubItems.Add("");

                    _backgroundWorker.ReportProgress(0, currentListViewItem);
                }
            }
        }

        private bool GetAvailableFreeSpace(string argNewPath)
        {
            using (ThreadContext.Stacks["NDC"].Push("GetAvailableFreeSpace"))
            {
                try
                {
                    _log.DebugFormat("Create a DriveInfo object for {0}.", argNewPath);
                    //Network shares with UNC names are not considered "Drives" and cannot be made into DriveInfo objects.
                    DriveInfo rootDriveInfo = new DriveInfo(argNewPath);

                    //Is device ready?
                    if (!rootDriveInfo.IsReady)
                    {
                        _log.DebugFormat("Drive is not ready.");
                        FileSystemListViewItem currentListViewItem = new FileSystemListViewItem("Device Not Ready...");
                        currentListViewItem.SubItems.Add("...");
                        currentListViewItem.SubItems.Add("...");

                        _backgroundWorker.ReportProgress(100, currentListViewItem);

                        _availableFreeSpace = 0;
                        return false;
                    }

                    _log.DebugFormat("Set the available drive space.");
                    _availableFreeSpace = rootDriveInfo.AvailableFreeSpace;
                }
                // ReSharper disable EmptyGeneralCatchClause
                catch { } //This can be empty because the only time it will happen is a UNC Path and the only thing we loose is the AvailableSpace.
                // ReSharper restore EmptyGeneralCatchClause


                return true;

            }
        }

        private void EnumerateShares(string argNewPath)
        {
            using (ThreadContext.Stacks["NDC"].Push("EnumerateShares"))
            {
                _log.DebugFormat("Get the enumerated shares.");
                //Get the network shares from the OS
                ShareCollection shareList = ShareCollection.GetShares(argNewPath);

                int currentPercent = 0;

                if (shareList != null)
                {
                    //Get the percentage of a single entry
                    decimal increment = 1 / (decimal)shareList.Count;

                    _log.DebugFormat("Report the shares to Background Worker.");
                    //Loop through the shares
                    foreach (ShareListViewItem currentShare in shareList)
                    {
                        currentPercent += (int)increment;
                        _backgroundWorker.ReportProgress(currentPercent, currentShare);
                    }
                }

            }
        }

        private void AddItems(IEnumerable<FileSystemInfo> argFileSystemInfos)
        {
            using (ThreadContext.Stacks["NDC"].Push("AddItems"))
            {
                FileSystemListViewItem currentListViewItem = null;

                _log.DebugFormat("Loop through the FileSystemInfo objects.");
                foreach (FileSystemInfo currentItem in argFileSystemInfos)
                {
                    if (_backgroundWorker.CancellationPending)
                    {
                        _log.DebugFormat("Background Worker canceling.");
                        return;
                    }

                    try
                    {
                        DirectoryInfo info = currentItem as DirectoryInfo;
                        if (info != null)
                        {
                            _log.DebugFormat("Add Directory: {0}.", info.FullName);
                            currentListViewItem = new DirectoryListViewItem(info);
                        }
                        else
                        {
                            _log.DebugFormat("Add File: {0}.", currentItem.FullName);
                            currentListViewItem = new FileListViewItem((FileInfo)currentItem);
                        }
                    }
                    // ReSharper disable EmptyGeneralCatchClause
                    catch { } //continue without throwing errors
                    // ReSharper restore EmptyGeneralCatchClause

                    //Interlocked.Increment(ref ItemFinishedCount);
                    _itemFinishedCount++;

                    int currentPercentage = (_itemFinishedCount / _itemCount) * 100;

                    _log.DebugFormat("Report items to the Background Worker.");
                    _backgroundWorker.ReportProgress(currentPercentage, currentListViewItem);
                }

                //_backgroundWorker.ReportProgress(100, null);

            }
        }

    } //public class FileSystemEngine
} // namespace FileSystemControls
